<?php

namespace App\DesignPatterns\Observer;

use App\Models\Currency;
use App\Models\Entrust;
use App\Models\Property;
use App\Models\PropertyInfo;

use Exception;

/**
 * 委托单匹配流程
 * 方法调用必须放在try catch 里
 * 必须使用事物处理
 * @access 未完成
 *
 * 第一步：匹配出能够跟当前委托单达成交易的所有委托单 （根据当前委托单的数量）
 * 第二步：按匹配顺序执行委托流程
 * 	1. 判断是全部完成还是部分完成
 * 	8. 生成交易单
 * 	
 * 	2. 修改当前委托单记录 （发送消息）
 * 	3. 修改当前用户对应资产（发送消息）
 * 	4. 写入当前用户资产详情记录
 * 	5. 修改匹配委托单记录（发送消息）
 * 	6. 修改匹配用户对应资产（发送消息）
 * 	7. 写入匹配用户资产详情记录
 */
class EntrustCollection
{
	//委托单
	public $entrust; 

	public function __construct($id)
	{
		$this->entrust = Entrust::find($id);
	}

	//执行入口
	public function entry()
	{
		//获取匹配成功委托单ID
		$ids = $this->GetEntrustIds();
		//处理匹配到的委托单
		if (collect($ids)->count() > 0) {
			//当前委托单数量
			$num = $this->entrust->num;
			$i = 0;
			do {
				$length = count($ids);
				//$i超过数组元素个数，跳出循环
				if ($i > ($length - 1)) {
					break;			
				}
				//按顺序查找匹配到的委托单
				$entrust = Entrust::find($ids[$i]);
				//匹配到的委托单剩余数量
				$rest = ($entrust->num - $entrust->deal);
				//匹配数量超过当前委托数量
				if ($rest >= $num) {
					//手续费
					$proc = $this->GetProc($this->entrust->cid, $num, $entrust->price);
					//总额
					$total = bcmul($num, $entrust->price, 8);
					//当前委托单为已完成
					$this->entrust->update(['status' => 3, 'deal' => $this->entrust->num]);
					//匹配委托单为完成或部分完成
					if ($rest == $num) {
						//匹配委托单已完成
						$entrust->update(['status' => 3, 'deal' => $entrust->num]);
					} else {
						//匹配委托单部分成交
						$entrust->update(['status' => 2]);
						$entrust->increment('deal', $num);
					}
					//当前委托是买入
					if ($this->entrust->type == 3) {
						//增加当前用户资产
						$this->HandleProperty($this->entrust->uid, $this->entrust->cid, $this->entrust->num, 1);
						//当前用户资产详情--委托买入获得(委托币种)
						$this->CreatePropertyInfo($this->entrust->uid, $this->entrust->cid, $this->entrust->id, 
							$this->entrust->num, $entrust->price, $total, $proc, 3, 1, 1);
						//当前用户资产详情--委托买入付款(USDT)
						$this->CreatePropertyInfo($this->entrust->uid, $this->get_standard(), $this->entrust->id, 
							$this->entrust->num, $entrust->price, $total, $proc, 3, 2, 2);
						//当前用户资产详情--委托买入手续费(USDT)
						$this->CreatePropertyInfo($this->entrust->uid, $this->get_standard(), $this->entrust->id, 
							$this->entrust->num, $entrust->price, $total, $proc, 3, 3, 3);
						//减去委托用户对应资产
						$this->HandleProperty($entrust->uid, $this->entrust->cid, $this->entrust->num, 2);
						/**
						 * @access 占位
						 */
					} else {
						//减去当前用户资产
						$this->HandleProperty($this->entrust->uid, $this->entrust->cid, $this->entrust->num, 2);
						//增加委托用户对应资产
						$this->HandleProperty($entrust->uid, $this->entrust->cid, $this->entrust->num, 1);
						/**
						 * @access 占位
						 */
					}					
				} else {
					//当前委托单为部分完成
					/**
					 * @access 占位
					 */
				}

				$i++;
				$num = bcsub($num, $rest, 8);	
			} while($num > 0);
		}
	}

	//获取USDT的币种ID
	private function get_standard()
	{
        return Currency::where(['is_standard' => 1, 'pay_switch' => 1, 'buy_switch' => 1])->value('id');
    }

	//获取满足条件的委托单ID集合
	protected function GetEntrustIds()
	{
		//匹配委托单结果ID合集
		$ids = [];
		//当前委托单数量
		$num = $this->entrust->num;
		do {
			//匹配委托单
			$entrust = $this->MatchingEntrust();
			//没有匹配到委托单，跳出循环
			if (collect($entrust)->count() == 0) {
				break;
			}
			//匹配到的委托单剩余数量
			$rest = ($entrust->num - $entrust->deal);
			//减去数量
			$num = bcsub($num, $rest, 8);
			//存储ID
			$ids[] = $entrust->id;
			//数量匹配完成
			if ($num <= 0) {
				break;
			}
		} while(0);

		return $ids;
	}

	//处理用户资产
	protected function HandleProperty($uid, $cid, $num, $type = 1)
	{
		$property = Property::where(['uid' => $uid, 'cid' => $cid])->first();

		if (collect($property)->count() == 0) {

			throw new Exception('资产不存在');
		}

		if ($type == 1) { //增加资产
			
			$property->increment('balance', $num); //增加余额

			$property->increment('total', $num); //增加总额

		} else if($type == 2) { //减去资产

			if ($property->balance < $num) {

				throw new Exception('资产不足');
			}

			$property->decrement('balance', $num); //减去余额

			$property->decrement('total', $num); //减去总额

		} else {

			throw new Exception('方法调用错误');
		}
	}

	//计算手续费
	protected function GetProc($cid, $num, $price)
	{
		$currency = Currency::find($cid);

		if (collect($currency)->count() == 0) {

			throw new Exception('币种不存在');
		}

		return bcmul(bcmul($num, $price, 8), bcdiv($currency->fee, 100), 8);
	}

	//写入资产详情表
	protected function CreatePropertyInfo($uid, $cid, $oid, $num, $price, $total, $proc, $type, $deal_type, $cate)
	{
		$note = $this->GetPropertyInfoNoteByCate($cate);

		PropertyInfo::create([
			'uid' => $uid,
			'cid' => $cid,
			'oid' => $oid,
			'num' => $num,
			'note' => $note,
			'price' => $price,
			'total' => $total,
			'proc' => $proc,
			'type' => $type,
			'deal_type' => $deal_type,
			'status' => 1,
		]);
	}

	//根据cate获取资产详情note说明
	private function GetPropertyInfoNoteByCate($cate) 
	{
		switch($cate) {
			case 1:
				return '委托买入获得';
				break;
			case 2:
				return '委托买入付款';
				break;
			case 3:
				return '委托买入手续费';
				break;
			case 4:
				return '委托卖出获得';
				break;
			case 5:
				return '委托卖出付款';
				break;
			case 6:
				return '委托卖出手续费';
				break;
			default:
				throw new Exception('cate参数错误');
		}
	}

	//写入订单表
	protected function CreateOrder($eid, $sid, $uid, $cid, $num, $proc, $price, $total, $type, $number_no)	
	{
		Order::create([
			'eid' => $eid,
			'uid' => $uid,
			'cid' => $cid,
			'num' => $num,
			'sid' => $sid,
			'proc' => $proc,
			'price' => $price,
			'total' => $total,
			'type' => $type,
			'order_no' => get_microtimes('JYD'),
			'number_no' => $number_no,
			'real_price' => ($total - $proc)
		]);
	}

	//获取对应满足条件的委托单
	protected function MatchingEntrust()
	{	
		//买入
		if ($this->entrust->type == 3) {
			//匹配卖出委托单
			$entrust = Entrust::where(['type' => 4, 'cid' => $this->entrust->cid])
						->where('price', '>=', $this->entrust->price)
						->whereIn('status', [1, 2])
						->orderBy('price', 'desc')
						->first();
		} else { 
			//匹配买入委托单
			$entrust = Entrust::where(['type' => 3, 'cid' => $this->entrust->cid])
						->where('price', '<=', $this->entrust->price)
						->whereIn('status', [1, 2])
						->orderBy('price', 'asc')
						->first();
		}

		return $entrust;
	}
}